import os
import shutil
import sys
import string
import xml
import xml.etree.ElementTree as Tree

generated_message_handler_register_hh='''\
////////////////////////////////////////////////
// MACHINE GENERATED, PLEASE DONOT MODIFY!    //
////////////////////////////////////////////////

#pragma once

#include "../../plugin/protocol/protocol_register.hh"
#include "../common/generated_proto_header.hh"

'''

generated_module_message_handler_register_cc='''\
////////////////////////////////////////////////
// MACHINE GENERATED, PLEASE DONOT MODIFY!    //
////////////////////////////////////////////////

#include "../../plugin/protocol/protocol_register.hh"
#include "generated_message_handler_register.hh"

'''

generated_protocol_register_cc='''\
////////////////////////////////////////////////
// MACHINE GENERATED, PLEASE DONOT MODIFY!
////////////////////////////////////////////////

#include "../../plugin/protocol/protocol_register.hh"
#include "../common/generated_message_id_defines.hh"
#include "../common/generated_proto_header.hh"
#include "generated_message_handler_register.hh"

Message * ProtocolRegister::getMessage(std::uint32_t msgID) {
    auto it = msgMap_.find(msgID);
    if (it == msgMap_.end()) {
        return nullptr;
    }
    return it->second;
}

std::uint32_t ProtocolRegister::getMessageID(const Message & msg) {
    auto address = reinterpret_cast<ptrdiff_t>(msg.GetDescriptor());
    auto it = typeMap_.find(address);
    if (it == typeMap_.end()) {
        return 0;
    }
    return it->second;
}

const std::string & ProtocolRegister::getError() const {
    return error_;
}

MsgHandler * ProtocolRegister::getHandler(std::uint32_t msgID) {
    auto it = handlerMap_.find(msgID);
    if (it == handlerMap_.end()) {
        return nullptr;
    }
    return it->second;
}

bool ProtocolRegister::doRegister() {
    return true;
}

'''

user_framework_hh ='''\
#pragma once

#include "../../plugin/base/framework.hh"
#include "../../corelib/singleton.hpp"

// User implemented framework
// @see Framework
class {module_name}Framework : public Framework {
    // TODO
};

// singleton of user framework
// NOTICE the name 'FrameworkRef' MUST NOT change
#define FrameworkRef (*kratos::corelib::Singleton<{module_name}Framework>::instance())
#define FrameworkSingleton kratos::corelib::Singleton<{module_name}Framework>
'''

user_framework_service_hh ='''\
#pragma once

#include "../../plugin/base/framework.hh"
#include "../../corelib/singleton.hpp"

// User implemented framework
// @see Framework
class {module_name}Framework : public Framework {
    virtual ModuleType onModuleType() override {
        return ModuleType::SERVICE_MODULE;
    }
    // TODO
};

// singleton of user framework
// NOTICE the name 'FrameworkRef' MUST NOT change
#define FrameworkRef (*kratos::corelib::Singleton<{module_name}Framework>::instance())
#define FrameworkSingleton kratos::corelib::Singleton<{module_name}Framework>
'''

user_framework_macro_hh ='''\

#ifdef verblog
#undef verblog
#define verblog (ModuleRef.getLogverb() << "[{module_name}]")
#endif // verblog
#ifdef debuglog
#undef debuglog
#define debuglog (ModuleRef.getLogdebug() << "[{module_name}]")
#endif // debuglog
#ifdef warnlog
#undef warnlog
#define warnlog (ModuleRef.getLogwarn() << "[{module_name}]")
#endif // warnlog
#ifdef errorlog
#undef errorlog
#define errorlog (ModuleRef.getLogerror() << "[{module_name}]")
#endif // errorlog
#ifdef fatallog
#undef fatallog
#define fatallog (ModuleRef.getLogfatal() << "[{module_name}]")
#endif // fatallog
#define endlog (ModuleRef.getLogend())

#ifdef logverb
#undef logverb
#endif // logverb
#ifdef logdebug
#undef logdebug
#endif // logdebug
#ifdef logwarn
#undef logwarn
#endif // logwarn
#ifdef logerror
#undef logerror
#endif // logerror
#ifdef logfatal
#undef logfatal
#endif // logfatal
'''

user_framework_cpp = '''\
#include "{module_name}_framework.hh"
#include "../../protocol_files/common/generated_proto_header.hh"

// TODO

'''

entry_hh='''\
////////////////////////////////////////////////
// MACHINE GENERATED, PLEASE DONOT MODIFY!    //
////////////////////////////////////////////////

#pragma once

#include "../../engine/module_define.hh"

FuncExport kratos::engine::Service* getService();
FuncExport void uninstallService();
FuncExport ModuleType getType();
'''

entry_cpp='''\
////////////////////////////////////////////////
// MACHINE GENERATED, PLEASE DONOT MODIFY!    //
////////////////////////////////////////////////

#include "generated_module_entry.hh"
#include "../../plugin/base/module_frame.hh"
#include "{module_name}_framework.hh"

kratos::engine::Service * getService() {
    ModuleRef.setFramework(&FrameworkRef);
    return &ModuleRef;
}

void uninstallService() {
    ProtocolRegisterSingleton::destroy();
    ObjectPoolManagerSinglton::destroy();
    FrameworkSingleton::destroy();
    ModuleSingleton::destroy();
}
'''

msg_handle_cpp = '''\
#include "../../plugin/protocol/protocol_register.hh"
#include "../../protocol_files/{module_name}/generated_message_handler_register.hh"
#include "{module_name}_framework.hh"
#include "../../plugin/base/entity.hh"

#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable: 4100)
#endif // _MSC_VER

// TODO user defined message handler

#ifdef _MSC_VER
#pragma warning(pop)
#endif
'''

cmakelists = '''\
cmake_minimum_required(VERSION 2.8)
project({module_name})

set(CMAKE_C_COMPILER "clang")
set(CMAKE_CXX_COMPILER "clang++")
set(CMAKE_CXX_FLAGS_DEBUG "-O0 -g -Wno-invalid-source-encoding")
set(CMAKE_CXX_FLAGS_RELEASE "-O3 -Wno-invalid-source-encoding")
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")

add_definitions("-m64 -fPIC -g -Wall -Wno-overloaded-virtual -fstack-protector -fstack-protector-all -Wno-unused-private-field -Wno-unused-value -Wno-reorder -Wno-unused-variable -Wno-missing-declarations")

INCLUDE_DIRECTORIES(
  /usr/local/include
)

SET(LIBRARY_OUTPUT_PATH ${PROJECT_SOURCE_DIR}/so)

aux_source_directory(./user/ SRC_LIST)
aux_source_directory(../protocol_files/common/protobuf_generated SRC_LIST)
aux_source_directory(../protocol_files/{module_name}/ SRC_LIST)

LINK_DIRECTORIES(
 /usr/local/lib/
 /usr/lib/
 /usr/lib64/
)

ADD_LIBRARY({module_name} SHARED ${SRC_LIST})

target_link_libraries(
	{module_name}
	-lpthread
	-lrt
	-ldl
	libplugin.a
	libprotobuf.a
	libkratos_core.a
)

INSTALL(TARGETS {module_name} DESTINATION so)
'''

class ModuleSrcGenerator:
    def generate_user_files(self, module_name, isService):
        self.isService = isService
        base_dir = '../' + module_name + '/user/'
        if not os.path.exists('../' + module_name):
            os.mkdir('../' + module_name)
        if not os.path.exists(base_dir):
            os.mkdir(base_dir)
        self.write_framework_hh(base_dir, module_name)
        self.write_framework_cpp(base_dir, module_name)
        self.write_msg_handle_cpp(base_dir, module_name)
        self.write_message_file(module_name)
        self.write_cmakefiles(module_name)
        self.write_entryfiles(base_dir, module_name)
        self.write_vs_proj_filter(module_name)
        self.write_vs_proj(module_name)

    def update_proj_files_only(self, module_name):
        self.write_vs_proj_filter(module_name)
        self.write_vs_proj(module_name)

    def write_framework_hh(self, base_dir, module_name):
        file = open(base_dir + module_name + "_framework.hh", 'w')
        if self.isService:
            file.write(user_framework_service_hh.replace('{module_name}', string.capitalize(module_name)))
        else:
            file.write(user_framework_hh.replace('{module_name}', string.capitalize(module_name)))
        file.write(user_framework_macro_hh.replace('{module_name}', module_name))
        file.close()

    def write_framework_cpp(self, base_dir, module_name):
        file = open(base_dir + module_name + "_framework.cpp", 'w')
        file.write(user_framework_cpp.replace('{module_name}', module_name))
        file.close()

    def write_msg_handle_cpp(self, base_dir, module_name):
        file = open(base_dir + module_name + "_default_msg_handler.cpp", 'w')
        file.write(msg_handle_cpp.replace('{module_name}', module_name))
        file.close()

    def write_message_file(self, module_name):
        if not os.path.exists('../protocol_files/' + module_name):
            os.mkdir('../protocol_files/' + module_name)
        file = open('../protocol_files/message/' + module_name + '.message', 'w')
        file.close()
        base_dir = '../protocol_files/' + module_name + '/'
        file = open(base_dir + 'generated_message_handler_register.hh', 'w')
        file.write(generated_message_handler_register_hh)
        file.close()
        file = open(base_dir + 'generated_module_message_handler_register.cc', 'w')
        file.write(generated_module_message_handler_register_cc)
        file.close()
        file = open(base_dir + 'generated_protocol_register.cc', 'w')
        file.write(generated_protocol_register_cc)
        file.close()

    def write_cmakefiles(self, module_name):
        file = open('../' + module_name + '/CMakeLists.txt', 'w')
        file.write(cmakelists.replace('{module_name}', module_name))
        file.close()

    def write_entryfiles(self, base_dir, module_name):
        file = open(base_dir + "generated_module_entry.hh", 'w')
        file.write(entry_hh)
        file.close()
        cppfile = open(base_dir + "generated_module_entry.cpp", 'w')
        cppfile.write(entry_cpp.replace('{module_name}', module_name))
        if self.isService:
            cppfile.write('ModuleType getType() {\n    return ModuleType::SERVICE_MODULE;\n}\n')
        else:
            cppfile.write('ModuleType getType() {\n    return ModuleType::EVENT_MODULE;\n}\n')
        cppfile.write('#define FrameworkSingleton kratos::corelib::Singleton<' + string.capitalize(module_name) + 'Framework>\n\n')
        cppfile.close()

    def delete_generated(self, root):
        for itemGroup in list(root):
            for clcompile in list(itemGroup):
                if clcompile.attrib.has_key('Include'):
                    if clcompile.attrib['Include'].find('generated_') > 0:
                        itemGroup.remove(clcompile)
                        continue

    def delete_user(self, root, module_name):
        for itemGroup in list(root):
            for clcompile in list(itemGroup):
                if clcompile.attrib.has_key('Include'):
                    if clcompile.attrib['Include'].find('user\\' + module_name + '_default_msg_handler.cpp') > 0:
                        itemGroup.remove(clcompile)
                        continue
                    elif clcompile.attrib['Include'].find('user\\' + module_name + '_framework.cpp') > 0:
                        itemGroup.remove(clcompile)
                        continue
                    elif clcompile.attrib['Include'].find('user\\' + module_name + '_framework.hh') > 0:
                        itemGroup.remove(clcompile)
                        continue

    def toprettyxml(self, element, indent, newline, level = 0):
        if element is not None:
            if element.text == None or element.text.isspace():
                element.text = newline + indent * (level + 1)
            else:
                element.text = element.text.strip()
        temp = list(element)
        for subelement in temp:
            if temp.index(subelement) < (len(temp) - 1):
                subelement.tail = newline + indent * (level + 1)
            else:
                subelement.tail = newline + indent * level
            self.toprettyxml(subelement, indent, newline, level =level + 1)

    def checkext(self, file):
        return (file.endswith('.cpp') or file.endswith('.cc'))

    def write_vs_proj_filter(self, module_name):
        Tree.register_namespace('', "http://schemas.microsoft.com/developer/msbuild/2003")
        if not os.path.exists('../vsproj/' + module_name + '/' + module_name + '.vcxproj.filters'):
            return
        filters = Tree.parse('../vsproj/' + module_name + '/' + module_name + '.vcxproj.filters')
        project = filters.getroot()
        self.delete_generated(project)
        self.delete_user(project, module_name)
        itemGroup = Tree.Element('ItemGroup')
        files = os.listdir('../protocol_files/common/')
        for file in files:
            if not self.checkext(file):
                continue
            file_path = os.path.join('../protocol_files/common/', file)
            if not os.path.isdir(file_path):
                include = Tree.Element('ClCompile')
                includeFilter = Tree.Element('Filter')
                includeFilter.text = 'readonly\\generated'
                include.attrib = {'Include':'..\\..\\protocol_files\\common\\' + file}
                include.append(includeFilter)
                itemGroup.append(include)
        files = os.listdir('../'+ module_name +'/user/')
        for file in files:
            file_path = os.path.join('../'+ module_name +'/user/', file)
            if not os.path.isdir(file_path):
                if not self.checkext(file):
                    continue
                if file.startswith('generated_'):
                    include = Tree.Element('ClCompile')
                    includeFilter = Tree.Element('Filter')
                    includeFilter.text = 'readonly\\generated'
                    include.attrib = {'Include':'..\\..\\' + module_name +'\\user\\' + file}
                    include.append(includeFilter)
                    itemGroup.append(include)
                else:
                    include = Tree.Element('ClCompile')
                    includeFilter = Tree.Element('Filter')
                    includeFilter.text = 'user'
                    include.attrib = {'Include':'..\\..\\' + module_name +'\\user\\' + file}
                    include.append(includeFilter)
                    itemGroup.append(include)
        files = os.listdir('../protocol_files/' + module_name)
        for file in files:
            if not self.checkext(file):
                continue
            file_path = os.path.join('../protocol_files/' + module_name +'/', file)
            if not os.path.isdir(file_path):
                include = Tree.Element('ClCompile')
                includeFilter = Tree.Element('Filter')
                includeFilter.text = 'readonly\\generated'
                include.attrib = {'Include':'..\\..\\protocol_files\\' + module_name +'\\' + file}
                include.append(includeFilter)
                itemGroup.append(include)
        project.append(itemGroup)
        file = open('../vsproj/' + module_name + '/' + module_name + '.vcxproj.filters', 'w')
        self.toprettyxml(project, '\t', '\n')
        filters.write(file, xml_declaration = True, encoding='utf-8', method='xml')
        file.close()

    def write_vs_proj(self, module_name):
        Tree.register_namespace('', "http://schemas.microsoft.com/developer/msbuild/2003")
        if not os.path.exists('../vsproj/' + module_name + '/' + module_name + '.vcxproj'):
            return
        filters = Tree.parse('../vsproj/' + module_name + '/' + module_name + '.vcxproj')
        project = filters.getroot()
        self.delete_generated(project)
        self.delete_user(project, module_name)
        itemGroup = Tree.Element('ItemGroup')
        files = os.listdir('../protocol_files/common/')
        for file in files:
            if not self.checkext(file):
                continue
            file_path = os.path.join('../protocol_files/common/', file)
            if not os.path.isdir(file_path):
                include = Tree.Element('ClCompile')
                include.attrib = {'Include':'..\\..\\protocol_files\\common\\' + file}
                itemGroup.append(include)
        files = os.listdir('../'+ module_name +'/user/')
        for file in files:
            if not self.checkext(file):
                continue
            file_path = os.path.join('../'+ module_name +'/user/', file)
            if not os.path.isdir(file_path):
                include = Tree.Element('ClCompile')
                include.attrib = {'Include':'..\\..\\' + module_name +'\\user\\' + file}
                itemGroup.append(include)
        files = os.listdir('../protocol_files/' + module_name)
        for file in files:
            if not self.checkext(file):
                continue
            file_path = os.path.join('../protocol_files/' + module_name +'/', file)
            if not os.path.isdir(file_path):
                include = Tree.Element('ClCompile')
                include.attrib = {'Include':'..\\..\\protocol_files\\' + module_name +'\\' + file}
                itemGroup.append(include)
        project.append(itemGroup)
        file = open('../vsproj/' + module_name + '/' + module_name + '.vcxproj', 'w')
        self.toprettyxml(project, '\t', '\n')
        filters.write(file, xml_declaration = True, encoding='utf-8', method='xml')
        file.close()

if __name__ == '__main__':
    gen = ModuleSrcGenerator()
    if len(sys.argv) == 3:
        if sys.argv[2] == '--update':
            gen.update_proj_files_only(sys.argv[1])
        elif sys.argv[2] == '--service':
            gen.generate_user_files(sys.argv[1], True)
    else:
        gen.generate_user_files(sys.argv[1], False)
